package analisadorSemantico;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import util.ArquivoUtil;
import analisadorLexico.TipoToken;
import analisadorSintatico.Node;

/**
 * @author Alan L Cardoso
 * 
 */
public class AnalisadorSemantico {

	private static List<Simbolo> terminalLiteral = new ArrayList<Simbolo>();

	private static List<Simbolo> terminalClasse = new ArrayList<Simbolo>();

	private static List<Simbolo> naoTerminais = new ArrayList<Simbolo>();

	private static Simbolo start = new Simbolo();

	private static Integer qtdLadoEsquero = 0;

	private static HashMap<String, Set<LadoDireito>> regras = new HashMap<String, Set<LadoDireito>>();

	private Set<LadoDireito> listaLadoDireito = new HashSet<>();

	private List<Simbolo> simbolos = new ArrayList<Simbolo>();

	private Node raiz;

	private List<String> erros = new ArrayList<String>();

	private String ladoEsquerdo;

	@SuppressWarnings("unused")
	private void exibirListas() {

		System.out.println("Start " + start);
		System.out.println("HashMap");
		Set<String> chaves = regras.keySet();
		for (String chave : chaves) {
			if (chave != null)
				System.out.println(chave + " ::= " + regras.get(chave));
		}

	}

	public void analisar(String caminhoArquivoLog, PrintWriter arquivoLog)
			throws IOException {

		if (!analisar()) {

			escreverMensagemPadraoErro(arquivoLog);

			for (String erro : this.erros) {
				ArquivoUtil.escreverArquivo(erro + "\n", arquivoLog);
			}

			ArquivoUtil
					.escreverArquivo(
							"\n== == == Fim da Lista de Erros == == ==\n\n",
							arquivoLog);

			ArquivoUtil.fecharArquivo(arquivoLog);
			System.exit(0);
		}
	}

	private void escreverMensagemPadraoErro(PrintWriter arquivoLog) {
		ArquivoUtil.escreverArquivo(
				"Foram Detectado Erros Durante a Análise Semântica.\n\n",
				arquivoLog);
		ArquivoUtil.escreverArquivo(
				"== == == Início da Lista de Erros == == ==\n\n", arquivoLog);
	}

	private boolean analisar() {

		listaRegra(this.raiz);
		// exibirListas();

		if (erros.isEmpty()) {
			return true;
		} else {
			return false;
		}

	}

	// <listaRegra> ::= <regra> <listaRegra> | E
	private Object listaRegra(Node node) {

		if (node.getFilhos().size() > 0) {
			regra(node.getFilho(0));
			listaRegra(node.getFilho(1));
		}

		return null;
	}

	// <regra> ::= NaoTerminal '::=' <listaLadoDireito>
	private Object regra(Node node) {

		this.ladoEsquerdo = node.getFilho(0).getToken().getImagem();

		if (qtdLadoEsquero == 0) {
			start = new Simbolo(node.getFilho(0).getToken().getImagem(), node
					.getFilho(0).getToken().getClasse()
					+ "");
		}

		qtdLadoEsquero++;

		listaLadoDireito(node.getFilho(2));

		if (!this.simbolos.isEmpty()) {
			this.listaLadoDireito.add(new LadoDireito(this.simbolos));
			this.simbolos = new ArrayList<>();
		}

		regras.put(this.ladoEsquerdo, this.listaLadoDireito);

		this.listaLadoDireito = new HashSet<>();

		this.simbolos = new ArrayList<>();

		return null;
	}

	// <listaLadoDireito> ::= <ladoDireito> '|' <listaLadoDireito> | E
	private Object listaLadoDireito(Node node) {

		if (node.getFilhos().size() != 0) {
			ladoDireito(node.getFilho(0));
		}

		if (node.getFilhos().size() > 1) {
			listaLadoDireito(node.getFilho(2));
		} else {
			// aqui basta verificar se o filho LadoDir é vazio!
			// System.out.println(node);
			if (node.getFilho(0).getFilhos().isEmpty()) {
				this.simbolos.add(new Simbolo("@@@@@", "CADEIA_VAZIA"));
			}
		}

		return null;
	}

	// <ladoDireito> ::= <simbolo> <ladoDireito> | E
	private Object ladoDireito(Node node) {

		if (node.getFilhos().size() > 0) {
			simbolo(node.getFilho(0));

			if (node.getFilho(1).getFilhos().isEmpty()) {

				ladoDireito(node.getFilho(1));

				if (!this.simbolos.isEmpty()) {
					listaLadoDireito.add(new LadoDireito(this.simbolos));
					simbolos = new ArrayList<>();
				}

			}

			ladoDireito(node.getFilho(1));

		}
		return null;
	}

	// <simbolo> ::= Terminal | NaoTerminal
	private Object simbolo(Node node) {

		if (node.getFilhos().size() == 0) {
			if (node.getToken().getClasse() == TipoToken.TERMINAL_CLASSE) {
				this.simbolos.add(new Simbolo(node.getToken().getImagem(), node
						.getToken().getClasse() + ""));

				terminalClasse.add(new Simbolo(node.getToken().getImagem(),
						node.getToken().getClasse() + ""));

				return node.getToken();
			}
		}

		this.simbolos.add(new Simbolo(node.getFilho(0).getToken().getImagem(),
				node.getFilho(0).getToken().getClasse() + ""));

		if (node.getFilho(0).getToken().getClasse() == TipoToken.NAO_TERMINAL) {
			naoTerminais
					.add(new Simbolo(node.getFilho(0).getToken().getImagem(),
							node.getFilho(0).getToken().getClasse() + ""));

		} else if (node.getFilho(0).getToken().getClasse() == TipoToken.TERMINAL_LITERAL) {
			terminalLiteral
					.add(new Simbolo(node.getFilho(0).getToken().getImagem(),
							node.getFilho(0).getToken().getClasse() + ""));
		}

		return node.getFilho(0).getToken();
	}

	public static List<Simbolo> getTerminalLiteral() {
		return terminalLiteral;
	}

	public static void setTerminalLiteral(List<Simbolo> terminalLiteral) {
		AnalisadorSemantico.terminalLiteral = terminalLiteral;
	}

	public static List<Simbolo> getTerminalClasse() {
		return terminalClasse;
	}

	public static void setTerminalClasse(List<Simbolo> terminalClasse) {
		AnalisadorSemantico.terminalClasse = terminalClasse;
	}

	public static HashMap<String, Set<LadoDireito>> getRegras() {
		return regras;
	}

	public static void setRegras(HashMap<String, Set<LadoDireito>> regras) {
		AnalisadorSemantico.regras = regras;
	}

	public List<String> getErros() {
		return erros;
	}

	public void setErros(List<String> erros) {
		this.erros = erros;
	}

	public AnalisadorSemantico(Node raiz) {
		this.raiz = raiz;
	}

	/**
	 * Método Para Verificar se todos os lados direitos não terminal tenha uma
	 * regra
	 * 
	 * @param gramatica
	 * 
	 * @param arquivoLog
	 */
	public void verificarRegras(PrintWriter arquivoLog) {

		Integer qtdErros = 0;
		for (Simbolo simbolo : naoTerminais) {
			if (!regras.containsKey(simbolo.getImagem())) {
				if (qtdErros == 0) {
					escreverMensagemPadraoErro(arquivoLog);
				}

				qtdErros++;
				ArquivoUtil.escreverArquivo("O Símbolo Não Terminal <"
						+ simbolo + ">, não possui REGRA. \n ", arquivoLog);

			}
		}

		if (qtdErros > 0) {
			System.out.println("Erro na análise semantica");
			ArquivoUtil.fecharArquivo(arquivoLog);
			System.exit(0);

		}
	}

}
